1 module lib.fs;
2 
3 /**
4   Copy the given source path recursively to the target path
5 
6   Params:
7       sourcePath = The source path which can be a file or a directory
8       targetPath = The target path which can be a file or a directory
9 */
10 void copyRecurse(string sourcePath, string targetPath)
11 {
12   import std.file : isDir, exists, SpanMode, dirEntries, mkdir, mkdirRecurse, copy;
13   import std.path : buildPath, absolutePath, relativePath, buildNormalizedPath;
14   import std.parallelism;
15 
16   if (!sourcePath.isDir)
17   {
18     return copy(sourcePath, targetPath);
19   }
20 
21   const sourcePathAbsolute = sourcePath.absolutePath.buildNormalizedPath;
22   mkdirRecurse(targetPath);
23   foreach (directoryEntry; dirEntries(sourcePathAbsolute, SpanMode.breadth).parallel)
24   {
25     auto targetEntry = buildPath(targetPath,
26         directoryEntry.name.absolutePath.relativePath(sourcePathAbsolute));
27     if (directoryEntry.isDir)
28     {
29       mkdir(targetEntry);
30     }
31     else
32     {
33       copy(directoryEntry.name, targetEntry);
34     }
35   }
36 }
37 
38 import std.file : DirEntry, write, read, tempDir, exists, SpanMode, dirEntries;
39 import std.path : buildPath, extension, baseName;
40 import std.digest : hexDigest;
41 import std.digest.crc : CRC32;
42 
43 /** Check if a folder has changed
44   Params:
45     rootDir = the directory to check
46     pattern = the pattern to check like "*.{d,di}"
47   Returns:
48     a boolean showing if the folder has changed
49 */
50 bool folderHasChanged(string rootDir, string pattern) @trusted
51 {
52   const cached_name = buildPath(tempDir(), hexDigest!CRC32(pattern ~ rootDir.baseName()));
53   const newCache = getUniqueHash(rootDir, pattern);
54   if (!cached_name.exists())
55   {
56     write(cached_name, newCache);
57     return true;
58   }
59   const changed = newCache != cast(ulong[]) read(cached_name);
60   if (changed)
61   {
62     write(cached_name, newCache);
63   }
64   return changed;
65 }
66 
67 /** Get a unique cache for a directory
68   Params:
69     rootDir = the directory to check
70     pattern = the pattern to check like "*.{d,di}"
71   Returns:
72     a buffer of `ulong[]`
73 */
74 ulong[] getUniqueHash(string rootDir, string pattern) @trusted
75 {
76   ulong[] buffer;
77   foreach (directoryEntry; dirEntries(rootDir, pattern, SpanMode.breadth))
78   {
79     buffer ~= getUniqueHash(directoryEntry);
80   }
81   return buffer;
82 }
83 
84 /// Get a unique hash for a DirEntry
85 /// https://github.com/WebFreak001/FSWatch/blob/1925700c64d9a26fbb2a6231b2cf94dc343800a4/source/fswatch.d#L38
86 ulong getUniqueHash(DirEntry entry) @trusted
87 {
88   version (Windows)
89     return entry.timeLastModified.stdTime ^ cast(ulong) entry.attributes;
90   else version (Posix)
91     return entry.statBuf.st_ino | (cast(ulong) entry.statBuf.st_dev << 32UL);
92   else
93     return (entry.timeLastModified.stdTime ^ (
94         cast(ulong) entry.attributes << 32UL) ^ entry.linkAttributes) * entry.size;
95 }
96 
97 unittest
98 {
99   copyRecurse("./dub.sdl", "./build/dub.sdl");
100   assert("./build/dub.sdl".exists);
101   copyRecurse("./packages", "./build");
102   assert("./build/packages".exists);
103 }
104 
105 /**
106   Move the given source path recursively to the target path
107 
108   Params:
109       sourcePath = The source path which can be a file or a directory
110       targetPath = The target path which can be a file or a directory
111 */
112 void moveRecurse(string sourcePath, string targetPath)
113 {
114   import std.file : isDir, exists, SpanMode, dirEntries, mkdir, mkdirRecurse,
115     rename, rmdirRecurse;
116   import std.path : buildPath, absolutePath, relativePath, buildNormalizedPath;
117   import std.parallelism;
118 
119   if (!sourcePath.isDir)
120   {
121     return rename(sourcePath, targetPath);
122   }
123 
124   const sourcePathAbsolute = sourcePath.absolutePath.buildNormalizedPath;
125   mkdirRecurse(targetPath);
126   foreach (directoryEntry; dirEntries(sourcePathAbsolute, SpanMode.breadth).parallel)
127   {
128     auto targetEntry = buildPath(targetPath,
129         directoryEntry.name.absolutePath.relativePath(sourcePathAbsolute));
130     if (directoryEntry.isDir)
131     {
132       mkdir(targetEntry);
133     }
134     else
135     {
136       rename(directoryEntry.name, targetEntry);
137     }
138   }
139   rmdirRecurse(sourcePath);
140 }
141 
142 import std.file : rmdir, dirEntries, FileException, setAttributes, remove, attrIsDir, exists;
143 
144 // TODO use dub to install rm-rf
145 //  https://github.com/WebFreak001/rm-rf/blob/master/source/rm/rf.d
146 
147 /**
148   Force remove the given directory recursively
149 
150   Params:
151       pathname = The directory which should be deleted
152 */
153 void rmdirRecurseForce(in char[] pathname)
154 {
155   if (!pathname.exists)
156   {
157     return;
158   }
159   //No references to pathname will be kept after rmdirRecurse,
160   //so the cast is safe
161   rmdirRecurseForce(DirEntry(cast(string) pathname));
162 }
163 
164 /// ditto
165 void rmdirRecurseForce(DirEntry de)
166 {
167   if (!de.isDir)
168     throw new FileException(de.name, "Not a directory");
169 
170   if (de.isSymlink)
171   {
172     version (Windows)
173       rmdir(de.name);
174     else
175       remove(de.name);
176   }
177   else
178   {
179     // all children, recursively depth-first
180     foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false))
181     {
182       version (Windows)
183       {
184         import core.sys.windows.windows;
185 
186         if ((e.attributes & FILE_ATTRIBUTE_READONLY) != 0)
187           setAttributes(e, e.attributes & ~FILE_ATTRIBUTE_READONLY);
188       }
189       attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name);
190     }
191 
192     // the dir itself
193     rmdir(de.name);
194   }
195 }